#ifndef FNT_SESSIONPRIV_H
#define FNT_SESSIONPRIV_H
#include <array>
#include "graph.priv.h"
#include "listmodel.h"
#include "labelListmodel.h"
#include "annot.tree.h"
#include "command.h"
#include "cubedata.h"
#include "load.thread.h"
#include "plugins/plugin.h"
#include "options.h"
#include "misc.h"
#include "stream.h"
#include "session.options.h"
#include "plugin.manager.h"
#include "compute.thread.h"
#include "viewer.h"
#include "tracer.h"
#include "session.h"
#include <QSlider>
#include <QTreeView>
#include <QListWidget>
#include <QLabel>
#include <QProgressBar>
#include <QComboBox>
#include <QTimer>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QAction>
#include <QMenu>
class Session;
class Viewer;

struct SessionPriv {
	Session* session;
	Viewer* viewer;
	SessionState state;

	std::array<QAction*, SESSION_ACTION_NUM> actions;
	QMenu* menuMisc;
	QMenu* popupViewer;
	QMenu* popupList;

	QWidget* sidePanel;
	QTreeView* listView;
	QTreeView* labelListView;
	QSlider* sliderCur;
	QPushButton* curLeft;
	QPushButton* curRight;
	QSlider* sliderTgt;
	QPushButton* tgtLeft;
	QPushButton* tgtRight;
	XfuncWidget* xfunc;
	XfuncWidget* xfunc2;

	const AnnotTree* annotTree;
	AnnotModel* annotModel;
	QListWidget* annotHeader;
	QComboBox* annotList;
	QTreeView* annotView;
	Cube annotCube;
	int annot_map;

	QStatusBar* statusBar;
	QLabel* statLabel;
	QProgressBar* progr;

	// Session
	LoadThreadInput image_input;
	QString file;
	int64_t verFile; /* -2 unreachable, -1, non-existant, 0... version */
	int64_t verSwap;
	int64_t ver;
	LoadThread* loadThread;
	ComputeThread* computeThread;

	// Tracing
	Graph graph;
	ListModel* listModel;
	LabelListModel* labelListModel;
	Path path;
	bool pathConnected;
	std::list<Command*> undoList;
	std::list<Command*> redoList;

	// SessionState and settings
	std::array<Cube, 9> cubes;
	std::array<std::pair<double, double>, 9> xfuncs;
	std::array<int, 9> cube_maps;
	std::array<double, 6> bbox;
	double mvol;
	Position curPos;
	Position tgtPos;
	double sight; // 100
	Options options;
	bool loadDataAuto;
	bool contrastAuto;
	bool connectAuto;
	int curRow;
	//int connectClickCount = 0;
	//int connectType = 0;
	//QTimer* m_ConnectTimer;

	QVector<QString> channels;

	QTimer* timer_5min;
	int64_t timer_check;

	SessionPriv(Session* s) :
		session{ s }, viewer{ nullptr }, state{ SessionState::Invalid },
		listView{ nullptr },
		labelListView{ nullptr },
		sliderCur{ nullptr }, curLeft{ nullptr }, curRight{ nullptr },
		sliderTgt{ nullptr }, tgtLeft{ nullptr }, tgtRight{ nullptr },
		annotTree{ nullptr }, annotModel{ nullptr }, annotView{ nullptr }, annotCube{}, annot_map{ 0 },
		statLabel{ nullptr }, progr{ nullptr },
		image_input{}, file{},
		verFile{ -1 }, verSwap{ -1 }, ver{ -1 },
		loadThread{ nullptr }, computeThread{ nullptr },
		graph{}, listModel{ nullptr },
		labelListModel{ nullptr },
		path{},
		undoList{}, redoList{},
		cubes{ Cube{}, }, cube_maps{ 0, },
		bbox{ qInf(), -qInf(), qInf(), -qInf(), qInf(), -qInf() },
		mvol{ qInf() },
		curPos{}, tgtPos{},
		options{}, loadDataAuto{ true }, contrastAuto{ true }, connectAuto{ false }, curRow{ -1 }, timer_5min{ nullptr }
	{
		viewer = new Viewer{ session };
		session->setCentralWidget(viewer);
		annot_map = 0;
		timer_5min = new QTimer{ session };
		QObject::connect(timer_5min, &QTimer::timeout, session, &Session::timeoutFiveMinute);
	}
	SessionPriv(Session* s, const LoadThreadInput& input) :
		SessionPriv{ s }
	{
		ver = 0;
		verFile = -1;
		verSwap = -1;
		//m_ConnectTimer = new QTimer(nullptr);
		//QObject::connect(m_ConnectTimer, &QTimer::timeout, session, &Session::connectDefault);
		setFile(QString{});

		graph = GraphPriv::alloc();
		image_input = input;
		options.setXform("image.xform", image_input.direction, image_input.origin);

		Tracer::instance()->logMessage("STAT: ", "SESSION_NEW");
	}
	SessionPriv(Session* s, const QString& file) :
		SessionPriv{ s }
	{
		verFile = 0;
		ver = 0;
		verSwap = -1;
		//m_ConnectTimer = new QTimer(nullptr);
		//QObject::connect(m_ConnectTimer, &QTimer::timeout, session, &Session::connectDefault);
		setFile(file);

		graph = GraphPriv::alloc();
		load(file, false);

		Tracer::instance()->logMessage("STAT: ", "SESSION_OPEN");
		dumpGraphDetail();
	}
	SessionPriv(Session* s, const QString& file, const QString& swap) :
		SessionPriv{ s }
	{
		verFile = -2;
		verSwap = 0;
		ver = 0;
		//m_ConnectTimer = new QTimer(nullptr);
		//QObject::connect(m_ConnectTimer, &QTimer::timeout, session, &Session::connectDefault);
		setFile(file);

		graph = GraphPriv::alloc();
		load(swap, true);

		Tracer::instance()->logMessage("STAT: ", "SESSION_OPEN_SWAP");
		dumpGraphDetail();
	}
	void postConstruct() {
		loadThread = new LoadThread{ session, image_input };
		loadThread->setupSources();
		std::array<double, 6> pbbox;
		loadThread->getBoundingBox(&pbbox);
		if (pbbox[1] > 20000 || pbbox[3] > 20000 || pbbox[5] > 20000) {
			auto options = session->options();
			options->setString("compute.find.default", "AStar");
			options->setDouble("compute.find.params.AStar.0", 0.7);
			options->setDouble("compute.find.params.AStar.1", 90.0);
			options->setDouble("compute.find.params.AStar.2", 30.0);
			options->setDouble("compute.find.params.AStar.3", 5.0);

			options->setString("compute.connect.default", "AStar");
			options->setDouble("compute.connect.params.AStar.0", 0.7);
			options->setDouble("compute.connect.params.AStar.1", 30.0);
			options->setDouble("compute.connect.params.AStar.2", 5.0);		

			options->setDouble("compute.connect.params.Simple.0", 0.5);
			options->setDouble("compute.connect.params.Simple.1", 10);

		}
		else {
			auto options = session->options();
			options->setString("compute.find.default", "AStar");
			options->setDouble("compute.find.params.AStar.0", 0.7);
			options->setDouble("compute.find.params.AStar.1", 90.0);
			options->setDouble("compute.find.params.AStar.2", 3.0);
			options->setDouble("compute.find.params.AStar.3", 0.5);
			
			options->setString("compute.connect.default", "AStar");
			options->setDouble("compute.connect.params.AStar.0", 0.7);
			options->setDouble("compute.connect.params.AStar.1", 3.0);
			options->setDouble("compute.connect.params.AStar.2", 0.5);

			options->setDouble("compute.connect.params.Simple.0", 0.5);
			options->setDouble("compute.connect.params.Simple.1", 1);
		}

		computeThread = new ComputeThread{ session };
		listModel = new ListModel{ session, graph };
		listView->setModel(listModel);
		QObject::connect(listView->selectionModel(), &QItemSelectionModel::currentRowChanged, session, &Session::currentChanged);
		listView->setEnabled(true);
		QObject::connect(listView->selectionModel(), &QItemSelectionModel::selectionChanged, session, &Session::selectionChanged);

		labelListModel = new LabelListModel{ session, graph };
		labelListView->setModel(labelListModel);
		QObject::connect(labelListView->selectionModel(), &QItemSelectionModel::currentRowChanged, session, &Session::labelCurrentChanged);
		labelListView->setEnabled(true);
		QObject::connect(labelListView->selectionModel(), &QItemSelectionModel::selectionChanged, session, &Session::labelSelectionChanged);

		QObject::connect(loadThread, &LoadThread::threadWarning, session, &Session::loadThreadWarning);
		QObject::connect(loadThread, &LoadThread::threadError, session, &Session::loadThreadError);
		QObject::connect(loadThread, &LoadThread::sourcesReady, session, &Session::sourcesReady);
		QObject::connect(loadThread, &LoadThread::cubeFinished, session, &Session::cubeFinished);
		QObject::connect(loadThread, &LoadThread::updateProgress, session, &Session::updateProgress);

		QObject::connect(computeThread, &ComputeThread::computeFinished, session, &Session::computeFinished);
		QObject::connect(computeThread, &ComputeThread::showProgress, session, &Session::showComputeProgress);
		QObject::connect(computeThread, &ComputeThread::threadWarning, session, &Session::computeThreadWarning);
		QObject::connect(computeThread, &ComputeThread::threadError, session, &Session::computeThreadError);

		timer_check = ver;
		timer_5min->setSingleShot(false);
		timer_5min->setInterval(5 * 60 * 1000);
		timer_5min->start();

		computeThread->start();
		loadThread->start();
		setState(SessionState::LoadingCatalog, "Loading catalog", true);
	}

	void setupSidePanel();
	void setupAnnotPanel();
	void setupStatusBar();
	void setupMenuBarAndToolBar();
	void setupUi();
	void setupActions();
	void setupMenus();

	void dumpGraphDetail() {
		std::vector<double> lengths(graph.trees().size() + 1, 0.0);
		std::vector<int64_t> segments(graph.trees().size() + 1, 0);
		std::vector<int> branches(graph.trees().size() + 1, 0);

		for (auto e : graph.edges()) {
			double len = 0;
			for (size_t i = 1; i<e.length(); i++) {
				len += e.points()[i].distTo(e.points()[i - 1]);
			}
			if (e.tree()) {
				lengths[e.tree().index()] += len;
				segments[e.tree().index()] += e.points().size() - 1;
			}
			else {
				lengths[graph.trees().size()] += len;
				segments[graph.trees().size()] += e.points().size() - 1;
			}
		}
		for (auto v : graph.vertices()) {
			if (!v.parentEdge())
				continue;
			if (v.neighbors().size() != 1)
				continue;
			if (v.tree()) {
				branches[v.tree().index()] += 1;
			}
			else {
				branches[graph.trees().size()] += 1;
			}
		}
		for (size_t i = 0; i<graph.trees().size() + 1; i++) {
			Tracer::instance()->logMessage("STAT: DETAIL: ", i, ": ", branches[i], "b, ", segments[i], "s, ", lengths[i], "um");
		}
		timer_check = ver;
	}

	~SessionPriv() {
		if (computeThread) {
			if (computeThread->isRunning()) {
				computeThread->stop();
				if (!computeThread->wait())
					computeThread->terminate();
			}
			delete computeThread;
		}
		if (loadThread) {
			if (loadThread->isRunning()) {
				loadThread->stop();
				if (!loadThread->wait()) {
					loadThread->terminate();
				}
			}
			delete loadThread;
		}
		if (graph) {
			for (auto e : graph.edges()) {
				viewer->recycleEdge(EdgePriv::get(e));
			}
			GraphPriv::free(graph);
		}

		//recentMenu.close(file);

		for (auto p : undoList)
			delete p;
		for (auto p : redoList)
			delete p;
	}

	void updateSlider(bool c, const Position& p) {
		auto slider = c ? sliderCur : sliderTgt;
		auto lft = c ? curLeft : tgtLeft;
		auto rgt = c ? curRight : tgtRight;
		if (p.edge) {
			bool prev = slider->blockSignals(true);
			slider->setMinimum(0);
			slider->setMaximum(p.edge.points().size() - 1);
			slider->setValue(p.index);
			lft->setEnabled(p.index != 0);
			rgt->setEnabled(p.index != p.edge.points().size() - 1);
			slider->setEnabled(true);
			slider->blockSignals(prev);
		}
		else {
			slider->setEnabled(false);
			lft->setEnabled(false);
			rgt->setEnabled(false);
		}
	}

	bool saveSession() {
		switch (state) {
		case SessionState::Invalid:
			return false;
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
		}
		if (ver == verFile) {
			showMessage("Save session", "Nothing new to save", session);
			return false;
		}
		if (file.isEmpty()) {
			return saveSessionAs();
		}
		try {
			saveOptions();
			save(file, false);
		}
		catch (const std::exception& e) {
			showWarning("Failed to save session", e.what(), session);
			updateActions();
			return false;
		}
		updateActions();
		Tracer::instance()->logMessage("STAT: ", "SESSION_SAVE");
		dumpGraphDetail();
		return true;
	}
	bool saveSessionAs() {
		switch (state) {
		case SessionState::Invalid:
			return false;
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
		}

		QFileDialog dlg(session, "Please choose a session file to save.", "", "");
		dlg.setAcceptMode(QFileDialog::AcceptSave);
		dlg.setDefaultSuffix("fnt");
		dlg.setFileMode(QFileDialog::AnyFile);
		dlg.setNameFilter("FNT Session file (*.fnt *.fntz)");
		if (dlg.exec() != QDialog::Accepted)
			return false;

		try {
			saveOptions();
			save(dlg.selectedFiles()[0], false);
		}
		catch (const std::exception& e) {
			showWarning("Failed to save session", e.what(), session);
			updateActions();
			return false;
		}
		updateActions();
		Tracer::instance()->logMessage("STAT: ", "SESSION_SAVE");
		dumpGraphDetail();
		return true;
	}

	bool readGraph(QObject* sender) {
		switch (state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return false;
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
		}

		auto a = qobject_cast<QAction*>(sender);
		if (!a)
			return false;
		bool ok;
		auto i = a->data().toInt(&ok);
		if (!ok)
			return false;

		try {
			std::unique_ptr<GraphReader> grd{ PluginManager::instance()->graphReaders()[i].first->creater() };
			for (int i = 0; i<4; i++) {
				CubeRef cuberef;
				auto& cube = i<3 ? cubes[3 * i] : annotCube;

				cuberef.data = nullptr;
				if (cube.data) {
					switch (cube.data.type()) {
					case CubeType::U8:
						cuberef.dataU8 = reinterpret_cast<const uint8_t*>(cube.data.data());
						cuberef.is16bit = false;
						break;
					case CubeType::U16:
						cuberef.dataU16 = reinterpret_cast<const uint16_t*>(cube.data.data());
						cuberef.is16bit = true;
						break;
					default:
						cuberef.data = nullptr;
					}
				}
				if (cuberef.data) {
					cuberef.width = cube.data.width();
					cuberef.height = cube.data.height();
					cuberef.depth = cube.data.depth();

					cuberef.widthAdj = cube.data.widthAdj();
					cuberef.heightAdj = cube.data.heightAdj();
					cuberef.depthAdj = cube.data.depthAdj();

					cuberef.xres = cube.xform.resolution[0];
					cuberef.yres = cube.xform.resolution[1];
					cuberef.zres = cube.xform.resolution[2];

					for (int i = 0; i<3; i++)
						cuberef.origin[i] = cube.xform.origin[i] + cube.xform.direction[i] * cube.data.cubeId().x0 + cube.xform.direction[i + 3] * cube.data.cubeId().y0 + cube.xform.direction[i + 6] * cube.data.cubeId().z0;
					for (int i = 0; i<9; i++)
						cuberef.dir[i] = cube.xform.direction[i];
					for (int i = 0; i<9; i++)
						cuberef.rdir[i] = cube.xform.direction_inv[i];
				}
				if (i<3) {
					grd->setupCube(i, cuberef, xfuncs[3 * i]);
				}
				else {
					grd->setupCubeAnnot(cuberef, annotTree ? &annotTree->annots() : nullptr);
				}
			}
			grd->read(graph, session);
		}
		catch (const std::exception& e) {
			showWarning("Failed to show session information", e.what(), session);
			return false;
		}
		return true;
	}

	bool closeSessionImp() {
		switch (state) {
		case SessionState::Invalid:
			return false;
		case SessionState::LoadingCatalog:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			showMessage("Close session", "Please cancel running operations first.", session);
			return false;
		case SessionState::Readonly:
		case SessionState::Ready:
			break;
		}

		if (ver != verFile) {
			QMessageBox mbox(QMessageBox::Question, "Save Session?", "Do you want to save changes to the current session?", QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard, session);
			mbox.setDefaultButton(QMessageBox::Save);
			auto res = mbox.exec();
			if (res == QMessageBox::Cancel) {
				return false;
			}
			if (res == QMessageBox::Save) {
				if (!saveSession()) {
					return false;
				}
			}
		}

		setState(SessionState::Invalid, "Ready to be destroyed.", false);

		Tracer::instance()->logMessage("STAT: ", "SESSION_CLOSE");
		dumpGraphDetail();
		return true;
	}

	void sourcesReady() {
		loadThread->getBoundingBox(&bbox);
		loadThread->getMinVoxelSize(&mvol);

		channels = loadThread->sources();

		if (!curPos.point.valid()) {
			if (bbox[0]>bbox[1]) {
				curPos.point.x(0);
			}
			else {
				curPos.point.x((bbox[0] + bbox[1]) / 2);
			}
			if (bbox[2]>bbox[3]) {
				curPos.point.y(0);
			}
			else {
				curPos.point.y((bbox[2] + bbox[3]) / 2);
			}
			if (bbox[4]>bbox[5]) {
				curPos.point.z(0);
			}
			else {
				curPos.point.z((bbox[4] + bbox[5]) / 2);
			}
			curPos.point.m = 0;
		}

		updateSlider(false, tgtPos);
		updateSlider(true, curPos);

#if 0
		{
			if (!options.getInt("channel.map.annot", &annot_map))
				annot_map = 0;
			auto b = annotList->blockSignals(true);
			annotList->clear();
			for (int j = 0; j<channels.size(); j++) {
				annotList->addItem(channels[j]);
			}
			annotList->setCurrentIndex(annot_map);
			annotList->blockSignals(b);
			annotList->setEnabled(true);
		}
#endif
		QString text = "Vol(um): ";
		double vsize[3];
		loadThread->getVoxelSize(vsize);
		text += "x:" + QString::number(vsize[0]);
		text += "  y:" + QString::number(vsize[1]);
		text += "  z:" + QString::number(vsize[2]);
		viewer->paintText(text.toStdString(), 3);
		QString text2 = "Size: ";
		text2 += "x:" + QString::number(bbox[1]);
		text2 += "  y:" + QString::number(bbox[3]);
		text2 += "  z:" + QString::number(bbox[5]);
		viewer->paintText(text2.toStdString(), 4);

		viewer->setSession(&options, graph, cubes, xfuncs, bbox, mvol);
		viewer->updatePosition(tgtPos, false);
		viewer->updatePosition(curPos, true);
		viewer->update();

		if (state == SessionState::LoadingCatalog)
			setState(SessionState::Ready, "Ready.", false);
		if (loadDataAuto)
			startDownloadCube();
	}
	void load(const QString& fn, bool swap) {
		viewer->paintText(fn.toStdString(), 2);

		InputStream fs;
		if (!fs.open(fn.toLocal8Bit().constData()))
			throwError("Failed to open file");
		fs.precision(13);
		fs.setf(std::ios::scientific);

		std::string line;
		fnt_getline(fs, line);
		if (!fs)
			throwError("Failed to read magic header");
		bool gzipped = false;
		if (line == FNTZ_MAGIC) {
			gzipped = true;
		}
		else if (line != FNT_MAGIC) {
			throwError("Wrong magic: ", line);
		}

		fnt_getline(fs, line);
		if (!fs)
			throwError("Failed to read image location line");
		image_input.location = QString::fromStdString(line);

		fnt_getline(fs, line);
		if (!fs)
			throwError("Failed to read line");
		while (line != "BEGIN_TRACING_DATA") {
			options.putLine(QString::fromStdString(line));
			fnt_getline(fs, line);
			if (!fs)
				throwError("Failed to read line");
		}

		if (gzipped) {
			if (!fs.pushGzip())
				throwError("Failed to start decompressing");
		}
		auto gp = GraphPriv::get(graph);
		gp->load(fs);
		if (gzipped) {
			if (!fs.pop())
				throwError("Failed to finish decompressing");
		}
		if (!fs.close())
			throwError("Failed to close file.");

		gp->updateModel();
		if (!options.getXform("image.xform", &image_input.direction, &image_input.origin)) {
			for (int i = 0; i<3; i++)
				for (int j = 0; j<3; j++)
					image_input.direction[i + 3 * j] = i == j ? 1 : 0;
			for (int i = 0; i<3; i++)
				image_input.origin[i] = 0;
		}

		for (auto e : graph.edges()) {
			auto& p0 = e.points()[0];
			updateBoundingBox(p0.x(), p0.y(), p0.z());
			auto& p1 = e.points()[e.points().size() - 1];
			updateBoundingBox(p1.x(), p1.y(), p1.z());
		}
	}
	void save(const QString& fn, bool swap) {
		printMessage("Saving ", fn, "...");
		OutputStream fs;
		if (!fs.open(fn.toLocal8Bit().constData()))
			throwError("Cannot open file");
		fs.precision(13);
		fs.setf(std::ios::scientific);

		bool gzipped = false;
		if (swap || fn.endsWith(".fntz") || fn.endsWith(".fnt.gz"))
			gzipped = true;

		if (gzipped)
			fs << FNTZ_MAGIC << '\n';
		else
			fs << FNT_MAGIC << '\n';
		if (!fs)
			throwError("Failed to write magic");

		fs << image_input.location << '\n';
		if (!fs)
			throwError("Failed to write image location");

		for (auto& line : options.getLines()) {
			fs << line << '\n';
			if (!fs)
				throwError("Failed to write option line");
		}

		fs << "BEGIN_TRACING_DATA" << '\n';
		if (!fs)
			throwError("Failed to write data start mark");

		if (gzipped) {
			if (!fs.pushGzip())
				throwError("Failed to start compressing");
		}
		GraphPriv::get(graph)->save(fs);
		if (gzipped) {
			if (!fs.pop())
				throwError("Failed to finish compressing");
		}
		if (!fs.close())
			throwError("Failed to close file");

		if (swap) {
			verSwap = ver;
		}
		else {
			//recentMenu.saveas(file, fn);
			verFile = ver;
			setFile(fn);
		}
	}
	void updateActions() {
		switch (state) {
		case SessionState::Invalid:
			actions[SESSION_SAVE]->setEnabled(false);
			actions[SESSION_SAVE_AS]->setEnabled(false);
			menuMisc->setEnabled(false);
			actions[SESSION_CLOSE]->setEnabled(false);
			actions[EDIT_UNDO]->setEnabled(false);
			actions[EDIT_REDO]->setEnabled(false);
			actions[EDIT_GOTO_POS]->setEnabled(false);
			actions[EDIT_GOTO_TARGET]->setEnabled(false);
			actions[EDIT_PICK_CURRENT]->setEnabled(false);
			actions[EDIT_LOAD_DATA]->setEnabled(false);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(false);
			actions[EDIT_CHANGE_TYPE]->setEnabled(false);
			actions[EDIT_CHANGE_MARK]->setEnabled(false);
			actions[EDIT_CHANGE_SIZE]->setEnabled(false);
			actions[EDIT_NEW_NEURON]->setEnabled(false);
			actions[EDIT_RENAME_NEURON]->setEnabled(false);
			actions[EDIT_DEL_NEURONS]->setEnabled(false);
			actions[EDIT_PURGE_NEURONS]->setEnabled(false);
			actions[EDIT_IMPORT_FNT]->setEnabled(false);
			actions[EDIT_IMPORT_SWC]->setEnabled(false);
			actions[EDIT_ABORT]->setEnabled(false);
			actions[TRACING_FIND]->setEnabled(false);
			actions[TRACING_CONNECT]->setEnabled(false);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(false);
			actions[TRACING_REFINE]->setEnabled(false);
			actions[TRACING_DONE]->setEnabled(false);
			actions[TRACING_CHECKED]->setEnabled(false);
			actions[TRACING_BRANCH]->setEnabled(false);
			actions[TRACING_EXTEND]->setEnabled(false);
			actions[TRACING_MARK1]->setEnabled(false);
			actions[TRACING_MARK2]->setEnabled(false);
			actions[TRACING_MARK3]->setEnabled(false);
			actions[TRACING_MARK4]->setEnabled(false);
			actions[TRACING_MARK_CLEAR]->setEnabled(false);
			actions[TRACING_DELETE]->setEnabled(false);
			actions[VIEW_SLICE]->setEnabled(false);
			actions[VIEW_EDGE]->setEnabled(false);
			actions[VIEW_CHANEL]->setEnabled(false);
			actions[DEBUG_SAVE_CUBE]->setEnabled(false);
			actions[DEBUG_SCREENSHOT]->setEnabled(false);
			actions[DEBUG_START_ROTATE]->setEnabled(false);
			break;
		case SessionState::LoadingCatalog:
			actions[SESSION_SAVE]->setEnabled(ver != verFile);
			actions[SESSION_SAVE_AS]->setEnabled(true);
			menuMisc->setEnabled(false);
			actions[SESSION_CLOSE]->setEnabled(true);
			actions[EDIT_UNDO]->setEnabled(false);
			actions[EDIT_REDO]->setEnabled(false);
			actions[EDIT_GOTO_POS]->setEnabled(false);
			actions[EDIT_GOTO_TARGET]->setEnabled(false);
			actions[EDIT_PICK_CURRENT]->setEnabled(false);
			actions[EDIT_LOAD_DATA]->setEnabled(false);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(false);
			actions[EDIT_CHANGE_TYPE]->setEnabled(false);
			actions[EDIT_CHANGE_MARK]->setEnabled(false);
			actions[EDIT_CHANGE_SIZE]->setEnabled(false);
			actions[EDIT_NEW_NEURON]->setEnabled(false);
			actions[EDIT_RENAME_NEURON]->setEnabled(false);
			actions[EDIT_DEL_NEURONS]->setEnabled(false);
			actions[EDIT_PURGE_NEURONS]->setEnabled(false);
			actions[EDIT_IMPORT_FNT]->setEnabled(false);
			actions[EDIT_IMPORT_SWC]->setEnabled(false);
			actions[EDIT_ABORT]->setEnabled(true);
			actions[TRACING_FIND]->setEnabled(false);
			actions[TRACING_CONNECT]->setEnabled(false);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(false);
			actions[TRACING_REFINE]->setEnabled(false);
			actions[TRACING_DONE]->setEnabled(false);
			actions[TRACING_CHECKED]->setEnabled(false);
			actions[TRACING_BRANCH]->setEnabled(false);
			actions[TRACING_EXTEND]->setEnabled(false);
			actions[TRACING_MARK1]->setEnabled(false);
			actions[TRACING_MARK2]->setEnabled(false);
			actions[TRACING_MARK3]->setEnabled(false);
			actions[TRACING_MARK4]->setEnabled(false);
			actions[TRACING_MARK_CLEAR]->setEnabled(false);
			actions[TRACING_DELETE]->setEnabled(false);
			actions[VIEW_SLICE]->setEnabled(false);
			actions[VIEW_EDGE]->setEnabled(false);
			actions[VIEW_CHANEL]->setEnabled(false);
			actions[DEBUG_SAVE_CUBE]->setEnabled(false);
			actions[DEBUG_SCREENSHOT]->setEnabled(false);
			actions[DEBUG_START_ROTATE]->setEnabled(false);
			break;
		case SessionState::Readonly:
		{
			actions[SESSION_SAVE]->setEnabled(ver != verFile);
			actions[SESSION_SAVE_AS]->setEnabled(true);
			menuMisc->setEnabled(false);
			actions[SESSION_CLOSE]->setEnabled(true);
			actions[EDIT_UNDO]->setEnabled(undoList.size()>0);
			actions[EDIT_REDO]->setEnabled(redoList.size()>0);
			actions[EDIT_GOTO_POS]->setEnabled(false);
			actions[EDIT_GOTO_TARGET]->setEnabled(false);
			actions[EDIT_PICK_CURRENT]->setEnabled(false);
			actions[EDIT_LOAD_DATA]->setEnabled(false);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(false);
			actions[EDIT_CHANGE_TYPE]->setEnabled(false);
			actions[EDIT_CHANGE_MARK]->setEnabled(false);
			actions[EDIT_CHANGE_SIZE]->setEnabled(false);
			actions[EDIT_NEW_NEURON]->setEnabled(false);
			actions[EDIT_RENAME_NEURON]->setEnabled(false);
			actions[EDIT_DEL_NEURONS]->setEnabled(false);
			actions[EDIT_PURGE_NEURONS]->setEnabled(false);
			actions[EDIT_IMPORT_FNT]->setEnabled(false);
			actions[EDIT_IMPORT_SWC]->setEnabled(false);
			actions[EDIT_ABORT]->setEnabled(false);
			actions[TRACING_FIND]->setEnabled(false);
			actions[TRACING_CONNECT]->setEnabled(false);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(false);
			actions[TRACING_REFINE]->setEnabled(false);
			actions[TRACING_DONE]->setEnabled(false);
			actions[TRACING_CHECKED]->setEnabled(false);
			actions[TRACING_BRANCH]->setEnabled(false);
			actions[TRACING_EXTEND]->setEnabled(false);
			actions[TRACING_MARK1]->setEnabled(false);
			actions[TRACING_MARK2]->setEnabled(false);
			actions[TRACING_MARK3]->setEnabled(false);
			actions[TRACING_MARK4]->setEnabled(false);
			actions[TRACING_MARK_CLEAR]->setEnabled(false);
			actions[TRACING_DELETE]->setEnabled(false);
			actions[VIEW_SLICE]->setEnabled(true);
			actions[VIEW_EDGE]->setEnabled(true);
			actions[VIEW_CHANEL]->setEnabled(true);
			actions[DEBUG_SAVE_CUBE]->setEnabled(true);
			actions[DEBUG_SCREENSHOT]->setEnabled(true);
			actions[DEBUG_START_ROTATE]->setEnabled(true);
		}
		break;
		case SessionState::Ready:
		{
			int16_t tgtMark = -1;
			bool tgtEdge = false;
			if (tgtPos.point.valid() && tgtPos.edge) {
				tgtEdge = true;
				tgtMark = tgtPos.edge.points()[tgtPos.index].m;
			}
			size_t nrnidx = 0;
			for (auto n : graph.trees()) {
				if (n.selected())
					nrnidx++;
			}
			bool curValid = curPos.point.valid();
			bool tgtValid = tgtPos.point.valid();
			actions[SESSION_SAVE]->setEnabled(ver != verFile);
			actions[SESSION_SAVE_AS]->setEnabled(true);
			menuMisc->setEnabled(true);
			actions[SESSION_CLOSE]->setEnabled(true);
			actions[EDIT_UNDO]->setEnabled(undoList.size()>0);
			actions[EDIT_REDO]->setEnabled(redoList.size()>0);
			actions[EDIT_GOTO_POS]->setEnabled(true);
			actions[EDIT_GOTO_TARGET]->setEnabled(tgtPos.point.valid());
			actions[EDIT_PICK_CURRENT]->setEnabled(true);
			actions[EDIT_LOAD_DATA]->setEnabled(true);
			actions[EDIT_LOAD_DATA]->setEnabled(true);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(true);
			actions[EDIT_CHANGE_TYPE]->setEnabled(tgtEdge);
			actions[EDIT_CHANGE_MARK]->setEnabled(tgtEdge);
			actions[EDIT_CHANGE_SIZE]->setEnabled(tgtEdge);
			actions[EDIT_NEW_NEURON]->setEnabled(tgtEdge && (tgtPos.index == 0 || tgtPos.index == tgtPos.edge.length() - 1));
			actions[EDIT_RENAME_NEURON]->setEnabled(curRow >= 0);
			actions[EDIT_DEL_NEURONS]->setEnabled(nrnidx>0);
			actions[EDIT_PURGE_NEURONS]->setEnabled(nrnidx>0);
			actions[EDIT_IMPORT_FNT]->setEnabled(true);
			actions[EDIT_IMPORT_SWC]->setEnabled(true);
			actions[EDIT_ABORT]->setEnabled(false);
			actions[TRACING_FIND]->setEnabled(curValid);
			actions[TRACING_CONNECT]->setEnabled(curValid&&tgtValid);
			actions[TRACING_REFINE]->setEnabled(path.points.size() >= 2);
			actions[TRACING_DONE]->setEnabled(true);
			actions[TRACING_CHECKED]->setEnabled(true);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(true);
			actions[TRACING_BRANCH]->setEnabled(path.points.size() >= 2);
			actions[TRACING_EXTEND]->setEnabled(path.points.size() >= 2);
			actions[TRACING_MARK1]->setEnabled(tgtEdge && tgtMark != 1);
			actions[TRACING_MARK2]->setEnabled(tgtEdge && tgtMark != 2);
			actions[TRACING_MARK3]->setEnabled(tgtEdge && tgtMark != 3);
			actions[TRACING_MARK4]->setEnabled(tgtEdge && tgtMark != 4);
			actions[TRACING_MARK_CLEAR]->setEnabled(tgtEdge && tgtMark != 0);
			actions[TRACING_DELETE]->setEnabled(curPos.edge || tgtEdge);
			actions[VIEW_SLICE]->setEnabled(true);
			actions[VIEW_EDGE]->setEnabled(true);
			actions[VIEW_CHANEL]->setEnabled(true);
			actions[DEBUG_SAVE_CUBE]->setEnabled(true);
			actions[DEBUG_SCREENSHOT]->setEnabled(true);
			actions[DEBUG_START_ROTATE]->setEnabled(true);

			actions[EDIT_CONTRAST_AUTO]->setEnabled(true);
			actions[TRACING_CONN_AUTO]->setEnabled(true);
		}
		break;
		case SessionState::Computing:
		{
			actions[SESSION_SAVE]->setEnabled(ver != verFile);
			actions[SESSION_SAVE_AS]->setEnabled(true);
			menuMisc->setEnabled(true);
			actions[SESSION_CLOSE]->setEnabled(true);
			actions[EDIT_UNDO]->setEnabled(false);
			actions[EDIT_REDO]->setEnabled(false);
			actions[EDIT_GOTO_POS]->setEnabled(true);
			actions[EDIT_GOTO_TARGET]->setEnabled(tgtPos.point.valid());
			actions[EDIT_PICK_CURRENT]->setEnabled(true);
			actions[EDIT_LOAD_DATA]->setEnabled(false);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(true);
			actions[EDIT_CHANGE_TYPE]->setEnabled(false);
			actions[EDIT_CHANGE_MARK]->setEnabled(false);
			actions[EDIT_CHANGE_SIZE]->setEnabled(false);
			actions[EDIT_NEW_NEURON]->setEnabled(false);
			actions[EDIT_RENAME_NEURON]->setEnabled(false);
			actions[EDIT_DEL_NEURONS]->setEnabled(false);
			actions[EDIT_PURGE_NEURONS]->setEnabled(false);
			actions[EDIT_IMPORT_FNT]->setEnabled(false);
			actions[EDIT_IMPORT_SWC]->setEnabled(false);
			actions[EDIT_ABORT]->setEnabled(true);
			actions[TRACING_FIND]->setEnabled(false);
			actions[TRACING_CONNECT]->setEnabled(false);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(false);
			actions[TRACING_REFINE]->setEnabled(false);
			actions[TRACING_DONE]->setEnabled(false);
			actions[TRACING_CHECKED]->setEnabled(false);
			actions[TRACING_BRANCH]->setEnabled(false);
			actions[TRACING_EXTEND]->setEnabled(false);
			actions[TRACING_MARK1]->setEnabled(false);
			actions[TRACING_MARK2]->setEnabled(false);
			actions[TRACING_MARK3]->setEnabled(false);
			actions[TRACING_MARK4]->setEnabled(false);
			actions[TRACING_MARK_CLEAR]->setEnabled(false);
			actions[TRACING_DELETE]->setEnabled(false);
			actions[VIEW_SLICE]->setEnabled(true);
			actions[VIEW_EDGE]->setEnabled(true);
			actions[VIEW_CHANEL]->setEnabled(true);
			actions[DEBUG_SAVE_CUBE]->setEnabled(true);
			actions[DEBUG_SCREENSHOT]->setEnabled(true);
			actions[DEBUG_START_ROTATE]->setEnabled(true);
		}
		break;
		case SessionState::LoadingCubes:
		{
			int16_t tgtMark = -1;
			bool tgtEdge = false;
			if (tgtPos.point.valid() && tgtPos.edge) {
				tgtEdge = true;
				tgtMark = tgtPos.edge.points()[tgtPos.index].m;
			}
			size_t nrnidx = 0;
			for (auto n : graph.trees()) {
				if (n.selected())
					nrnidx++;
			}
			actions[SESSION_SAVE]->setEnabled(ver != verFile);
			actions[SESSION_SAVE_AS]->setEnabled(true);
			menuMisc->setEnabled(true);
			actions[SESSION_CLOSE]->setEnabled(true);
			actions[EDIT_UNDO]->setEnabled(undoList.size()>0);
			actions[EDIT_REDO]->setEnabled(redoList.size()>0);
			actions[EDIT_GOTO_POS]->setEnabled(true);
			actions[EDIT_GOTO_TARGET]->setEnabled(tgtPos.point.valid());
			actions[EDIT_PICK_CURRENT]->setEnabled(true);
			actions[EDIT_LOAD_DATA]->setEnabled(false);
			actions[EDIT_LOAD_DATA_AUTO]->setEnabled(true);
			actions[EDIT_CHANGE_TYPE]->setEnabled(tgtEdge);
			actions[EDIT_CHANGE_MARK]->setEnabled(tgtEdge);
			actions[EDIT_CHANGE_SIZE]->setEnabled(tgtEdge);
			actions[EDIT_NEW_NEURON]->setEnabled(tgtEdge && (tgtPos.index == 0 || tgtPos.index == tgtPos.edge.length() - 1));
			actions[EDIT_RENAME_NEURON]->setEnabled(curRow >= 0);
			actions[EDIT_DEL_NEURONS]->setEnabled(nrnidx>0);
			actions[EDIT_PURGE_NEURONS]->setEnabled(nrnidx>0);
			actions[EDIT_IMPORT_FNT]->setEnabled(true);
			actions[EDIT_IMPORT_SWC]->setEnabled(true);
			actions[EDIT_ABORT]->setEnabled(true);
			actions[TRACING_FIND]->setEnabled(false);
			actions[TRACING_CONNECT]->setEnabled(false);
			actions[TRACING_REFINE]->setEnabled(false);
			actions[TRACING_DONE]->setEnabled(true);
			actions[TRACING_CHECKED]->setEnabled(true);
			actions[TRACING_CONNECT_SIMPLE]->setEnabled(true);
			actions[TRACING_BRANCH]->setEnabled(path.points.size() >= 2);
			actions[TRACING_EXTEND]->setEnabled(path.points.size() >= 2);
			actions[TRACING_MARK1]->setEnabled(tgtEdge && tgtMark != 1);
			actions[TRACING_MARK2]->setEnabled(tgtEdge && tgtMark != 2);
			actions[TRACING_MARK3]->setEnabled(tgtEdge&& tgtMark != 3);
			actions[TRACING_MARK4]->setEnabled(tgtEdge && tgtMark != 4);
			actions[TRACING_MARK_CLEAR]->setEnabled(tgtEdge && tgtMark != 0);
			actions[TRACING_DELETE]->setEnabled(curPos.edge || tgtEdge);
			actions[VIEW_SLICE]->setEnabled(true);
			actions[VIEW_EDGE]->setEnabled(true);
			actions[VIEW_CHANEL]->setEnabled(true);
			actions[DEBUG_SAVE_CUBE]->setEnabled(true);
			actions[DEBUG_SCREENSHOT]->setEnabled(true);
			actions[DEBUG_START_ROTATE]->setEnabled(true);
		}
		break;
		}
	}
	void setState(SessionState stt, const QString& desc, bool p) {
		state = stt;
		if (p) {
			progr->setMaximum(0);
			progr->setValue(0);
			progr->setEnabled(true);
		}
		else {
			progr->setEnabled(false);
			progr->setMaximum(1);
			progr->setValue(0);
		}
		statLabel->setText(desc);
		//setCursor(QCursor(Qt::ArrowCursor));
		updateActions();
		Q_EMIT session->stateChanged(session, state);
	}
	void setFile(const QString& s) {
		file = s;
		if (file.isNull()) {
			if (ver != verFile) {
				session->setWindowTitle("[Untitled]*");
			}
			else {
				session->setWindowTitle("[Untitled]");
			}
		}
		else {
			if (ver != verFile) {
				session->setWindowTitle(QDir{ file }.dirName() + '*');
			}
			else {
				session->setWindowTitle(QDir{ file }.dirName());
			}
		}
	}
	void updateAnnotLabel() {
		double pos[3] = { tgtPos.point.x(), tgtPos.point.y(), tgtPos.point.z() };
		for (int i = 0; i<3; i++) {
			pos[i] -= annotCube.xform.origin[i] + annotCube.xform.direction[i] * annotCube.data.cubeId().x0 + annotCube.xform.direction[i + 3] * annotCube.data.cubeId().y0 + annotCube.xform.direction[i + 6] * annotCube.data.cubeId().z0;
		}
		int32_t posi[3];
		for (int i = 0; i<3; i++) {
			posi[i] = pos[0] * annotCube.xform.direction_inv[i] + pos[1] * annotCube.xform.direction_inv[3 + i] + pos[2] * annotCube.xform.direction_inv[6 + i];
		}
		if (posi[0]<0 || posi[0] >= annotCube.data.width())
			return;
		if (posi[1]<0 || posi[0] >= annotCube.data.height())
			return;
		if (posi[2]<0 || posi[0] >= annotCube.data.depth())
			return;
		auto annoti = posi[0] + annotCube.data.widthAdj()*(posi[1] + annotCube.data.heightAdj()*posi[2]);
		uint32_t id;
		switch (annotCube.data.type()) {
		case CubeType::U8:
			id = reinterpret_cast<const uint8_t*>(annotCube.data.data())[annoti];
			break;
		case CubeType::U16:
			id = reinterpret_cast<const uint16_t*>(annotCube.data.data())[annoti];
			break;
		case CubeType::U32:
			id = reinterpret_cast<const uint32_t*>(annotCube.data.data())[annoti];
			break;
		default:
			id = 0;
		}
		std::vector<const AnnotItem*> annots;
		auto n = annotTree->annot(id);
		while (n) {
			annots.push_back(n);
			n = annotTree->annot(n->par_idx);
		}
		annotHeader->clear();
		while (!annots.empty()) {
			auto name = QString::fromStdString(annots.back()->name);
			annotHeader->addItem(name);
			annots.pop_back();
		}
		annotHeader->setCurrentRow(annotHeader->count() - 1);
	}
	void pickPosition(Edge edg, size_t idx, const Point* pos) {
		bool chg = false;
		if (tgtPos.edge != edg) {
			tgtPos.edge = edg;
			chg = true;
		}
		const Point* p = nullptr;
		if (edg) {
			if (tgtPos.index != idx) {
				tgtPos.index = idx;
				chg = true;
			}
			p = &edg.points()[idx];
		}
		else {
			p = pos;
		}
		if (p) {
			if (!(tgtPos.point == *p)) {
				tgtPos.point = *p;
				chg = true;
			}
		}
		if (chg) {
			updateSlider(false, tgtPos);
			viewer->updatePosition(tgtPos, false);
			if (annotCube.data)
				updateAnnotLabel();
			if (tgtPos.point.valid() && tgtPos.edge && tgtPos.edge.tree()) {
				curRow = tgtPos.edge.tree().index();
				auto idx = listModel->index(curRow, 0, {});
				listView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
			}
		}
	} // update(actions/viewer)
	void connectIfPossible() {
		if (!connectAuto)
			return;
		if (!cubes[0].data)
			return;
		if (!curPos.edge)
			return;
		if (tgtPos.edge)
			return;
		if (!actions[TRACING_CONNECT]->isEnabled())
			return;
		printMessage("auto connect");
		actions[TRACING_CONNECT]->trigger();
	}
	// XXX do this when graph changed(edge changed): priv->path.path.clear();
	// change current edge binding
	// change target edge binding
	void jumpToPosition(Edge edg, size_t idx, const Point* pos) {
		curPos.edge = edg;
		const Point* p = nullptr;
		if (edg) {
			curPos.index = idx;
			p = &edg.points()[idx];
		}
		else {
			p = pos;
		}
		//
		if (p) {
			if (p->valid()) {
				curPos.point = *p;
				viewer->clearSelection();
			}
		}
		updateSlider(true, curPos);
		path.points.clear(); // path is valid only when it's in current cube
		if (viewer) {
			viewer->updatePosition(curPos, true);
			viewer->setPathData(path);
		}
		if (computeThread) {
			computeThread->positionChanged();
		}
	}
	void updateVersion(int64_t v, bool branching) {
		if (branching) {
			if (verFile>ver)
				verFile = -2;
			if (verSwap>ver)
				verSwap = -2;
		}
		ver += v;
		setFile(file);
	}
	void runCommand(Command* cmd) {
		if (redoList.size()>0) {
			for (auto p : redoList) {
				delete p;
			}
			redoList.clear();
		}
		cmd->redo(this);
		undoList.push_front(cmd);
		while (undoList.size()>100) {
			auto p = undoList.back();
			delete p;
			undoList.pop_back();
		}
		updateVersion(1, true);
		updateActions();
		viewer->update();
		if (state == SessionState::Ready && loadDataAuto)
			session->startDownloadCube();
	}

	bool attachOptionsDlg() {
		viewer->attachOptionsDlg();
		computeThread->attachOptionsDlg();
		SessionChannelOptions::instance()->attach(this);
		SessionLoadingOptions::instance()->attach(this);
		return true;
	}
	bool detachOptionsDlg() {
		viewer->detachOptionsDlg();
		computeThread->detachOptionsDlg();
		SessionChannelOptions::instance()->attach(nullptr);
		SessionLoadingOptions::instance()->attach(nullptr);
		return true;
	}

	bool updateCube(int ch, int idx, const std::pair<double, double>& xfunc) {
		if (idx <= 0) {
			cube_maps[ch] = idx;
			cubes[ch] = Cube{};
			viewer->updateCube(ch, cubes[ch], xfunc);
			if (ch % 3 == 0)
				computeThread->updateCube(ch / 3, cubes[ch], xfunc);
		}
		else if (idx != cube_maps[ch]) {
			cube_maps[ch] = idx;
			cubes[ch] = Cube{};
			xfuncs[ch] = xfunc;
			viewer->updateCube(ch, cubes[ch], xfuncs[ch]);
			if (ch % 3 == 0)
				computeThread->updateCube(ch / 3, cubes[ch], xfuncs[ch]);
		}
		else {
			xfuncs[ch] = xfunc;
			viewer->updateXfunc(ch, xfuncs[ch]);
			if (ch % 3 == 0)
				computeThread->updateXfunc(ch / 3, xfuncs[ch]);
		}
		return true;
	}
	bool updateXfunc(int ch, const std::pair<double, double>& xfunc) {
		if (cube_maps[ch] <= 0) {
			viewer->updateXfunc(ch, xfunc);
			if (ch % 3 == 0)
				computeThread->updateXfunc(ch / 3, xfunc);
		}
		else {
			xfuncs[ch] = xfunc;
			viewer->updateXfunc(ch, xfuncs[ch]);
			if (ch % 3 == 0)
				computeThread->updateXfunc(ch / 3, xfuncs[ch]);
		}
		return true;
	}
	bool startDownloadCube() {
		switch (state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			return false;
		case SessionState::Ready:
			break;
		}
		bool needDownload = false;
		for (int i = 0; i<9; i++) {
			if (cube_maps[i] != 0) {
				if (loadThread->setPosition(i, cube_maps[i], curPos.point.x(), curPos.point.y(), curPos.point.z(), sight, cubes[i])) {
					printMessage("Need to download ", cube_maps[i], '/', i);
					needDownload = true;
				}
			}
		}
		if (annot_map != 0) {
			if (loadThread->setPosition(10, annot_map, curPos.point.x(), curPos.point.y(), curPos.point.z(), sight, annotCube)) {
				printMessage("Need to download ", annot_map);
				needDownload = true;
			}
		}
		if (needDownload) {
			setState(SessionState::LoadingCubes, "Downloading cubes.", true);
		}
		return true;
	}

	void viewerReady(bool err) {
		if (err) {
			showWarning("Failed to initialize 3D viewer", "Failed to load 3D viewer.", session);
		}
		else {
			postConstruct();
		}
		SessionChannelOptions::instance()->config(this, &options);
		SessionLoadingOptions::instance()->config(this, &options);
		Q_EMIT session->sessionReady(session, err);
	}

	void loadOptions() {
		auto goptions = Tracer::instance()->options();

		int loaddataauto = 1;
		if (!options.getInt("core.auto.load.data", &loaddataauto))
			goptions->getInt("core.auto.load.data", &loaddataauto);
		loadDataAuto = loaddataauto;
		int contrastauto = 1;
		if (!options.getInt("core.auto.contrast", &contrastauto))
			goptions->getInt("core.auto.contrast", &contrastauto);
		contrastAuto = contrastauto;

		int idx, edg;
		if (options.getInt("core.target.edge", &edg)
			&& options.getInt("core.target.idx", &idx)) {
			size_t ei = edg, pi = idx;
			if (ei<graph.edges().size() && pi<graph.edges()[ei].length()) {
				tgtPos.edge = graph.edges()[ei];
				tgtPos.index = pi;
				tgtPos.point = tgtPos.edge.points()[pi];
			}
			else {
				tgtPos.point.m = -1;
			}
		}
		else if (options.getXyzInt("core.target.pos", &tgtPos.point._x, &tgtPos.point._y, &tgtPos.point._z)) {
			tgtPos.point.m = 0;
		}
		else {
			tgtPos.point.m = -1;
		}
		if (options.getInt("core.current.edge", &edg)
			&& options.getInt("core.current.idx", &idx)) {
			size_t ei = edg, pi = idx;
			if (ei<graph.edges().size() && pi<graph.edges()[ei].length()) {
				curPos.edge = graph.edges()[ei];
				curPos.index = pi;
				curPos.point = curPos.edge.points()[pi];
			}
			else {
				curPos.point.m = -1;
			}
		}
		else if (options.getXyzInt("core.current.pos", &curPos.point._x, &curPos.point._y, &curPos.point._z)) {
			curPos.point.m = 0;
		}
		else {
			curPos.point.m = -1;
		}

		//
		// XXX Options::config
		//options.getDouble("core.cube.size", &volume.sight);
		//volume.setSight(volume.sight);
	}
	void saveOptions() {
		{
			if (annot_map>0) {
				options.setInt("channel.map.annot", annot_map);
			}
			else {
				options.removeKey("channel.map.annot");
			}
		}
		SessionChannelOptions::instance()->saveConfig(this, &options);
		SessionLoadingOptions::instance()->saveConfig(this, &options);
		viewer->saveOptions(&options);
		computeThread->saveOptions(&options);
		// XXX OptionsSight::saveConfig
		//XXX pull options

		if (tgtPos.point.valid()) {
			if (tgtPos.edge) {
				options.setInt("core.target.edge", tgtPos.edge.index());
				options.setInt("core.target.idx", tgtPos.index);
			}
			else {
				options.setXyzInt("core.target.pos", tgtPos.point._x, tgtPos.point._y, tgtPos.point._z);
				options.removeKey("core.target.edge");
			}
		}
		else {
			options.removeKey("core.target.edge");
			options.removeKey("core.target.pos.x");
		}
		if (curPos.point.valid()) {
			if (curPos.edge) {
				options.setInt("core.current.edge", curPos.edge.index());
				options.setInt("core.current.idx", curPos.index);
			}
			else {
				options.setXyzInt("core.current.pos", curPos.point._x, curPos.point._y, curPos.point._z);
				options.removeKey("core.current.edge");
			}
		}
		else {
			options.removeKey("core.current.edge");
			options.removeKey("core.current.pos.x");
		}

		options.setInt("core.auto.load.data", loadDataAuto);
		options.setInt("core.auto.contrast", contrastAuto);
	}

	bool updateBoundingBox(double x, double y, double z) {
		bool expanded{ false };
		if (x<bbox[0]) {
			bbox[0] = x; expanded = true;
		}
		if (x>bbox[1]) {
			bbox[1] = x; expanded = true;
		}
		if (y<bbox[2]) {
			bbox[2] = y; expanded = true;
		}
		if (y>bbox[3]) {
			bbox[3] = y; expanded = true;
		}
		if (z<bbox[4]) {
			bbox[4] = z; expanded = true;
		}
		if (z>bbox[5]) {
			bbox[5] = z; expanded = true;
		}
		return expanded;
	}
	bool updateBoundingBox(const Cube& cube) {
		int32_t p0[] = { cube.data.cubeId().x0, cube.data.cubeId().y0, cube.data.cubeId().z0 };
		double pl[3];
		double pr[3];
		for (int dir = 0; dir<3; dir++) {
			double rr = cube.xform.origin[dir];
			double ll = rr;
			for (int k = 0; k<3; k++) {
				ll += cube.xform.direction[dir + k * 3] * p0[k];
				rr += cube.xform.direction[dir + k * 3] * p0[k];
				if (cube.xform.direction[dir + k * 3] >= 0) {
					rr += cube.xform.direction[dir + k * 3] * cube.data.size(k);
				}
				else {
					ll += cube.xform.direction[dir + k * 3] * cube.data.size(k);
				}
			}
			pl[dir] = ll;
			pr[dir] = rr;
		}
		return updateBoundingBox(pl[0], pl[1], pl[2]) || updateBoundingBox(pr[0], pr[1], pr[2]);
	}
	void updateAnnotModel(const AnnotTree* t) {
		auto prevm = annotModel;
		if (prevm && annotTree == t)
			return;

		annotTree = t;
		annotModel = new AnnotModel{ session, *t };
		annotView->setModel(annotModel);
		QObject::connect(annotView->selectionModel(), &QItemSelectionModel::currentRowChanged, session, &Session::currentChanged);
		annotView->setEnabled(true);
		//QObject::connect(annotView->selectionModel(), &QItemSelectionModel::selectionChanged, session, &Session::selectionChanged);
		if (prevm)
			delete prevm;
	}
	void annotListChanged(int idx) {
		if (idx != annot_map) {
			annot_map = idx;
			annotView->setModel(nullptr);
			if (annotModel) {
				delete annotModel;
				annotModel = nullptr;
				annotTree = nullptr;
			}
			annotCube = Cube{};
			if (loadDataAuto)
				startDownloadCube();
		}
	}
};
#endif // !FNT_SESSIONPRIV_H
